﻿namespace LiveLabs.Logging
{
    using Xunit;
    using System;

    public class SettingsFixture
    {
        #region Log Level Tests

        [Fact]
        public void Should_Have_Default_Log_Level_Inform()
        {
            Settings set = new Settings();
            Assert.Equal(LogLevel.Inform, set.DefaultLogLevel);
        }

        [Fact]
        public void Should_Return_Default_Log_Level_For_Non_Registered_Logger_Name()
        {
            Settings set = new Settings();
            Assert.Equal(set.DefaultLogLevel, set.FindLogLevelFor("a.b.c.d"));
        }

        [Fact]
        public void Should_Return_Default_Log_Level_For_Non_Registered_Logger_Type()
        {
            Settings set = new Settings();
            Assert.Equal(set.DefaultLogLevel, set.FindLogLevelFor(typeof(Foo)));
        }

        [Fact]
        public void Should_Return_Default_Log_Level_For_Non_Registered_Logger_Type_Using_Generic()
        {
            Settings set = new Settings();
            Assert.Equal(set.DefaultLogLevel, set.FindLogLevelFor<Foo>());
        }

        [Fact]
        public void Should_Return_Registered_Log_Level_For_Logger_Name()
        {
            const string loggerName = "a.b.c.d";

            Settings set = new Settings();
            set.RegisterLogLevelFor(loggerName, LogLevel.Verbose);

            Assert.Equal(LogLevel.Verbose, set.FindLogLevelFor(loggerName));
        }

        [Fact]
        public void Should_Return_Registered_Log_Level_For_Logger_Type()
        {
            Type typeForLogger = typeof(Foo);

            Settings set = new Settings();
            set.RegisterLogLevelFor(typeForLogger, LogLevel.Verbose);

            Assert.Equal(LogLevel.Verbose, set.FindLogLevelFor(typeForLogger));
        }

        [Fact]
        public void Should_Return_Registered_Log_Level_For_Logger_Type_Using_Generic()
        {
            Settings set = new Settings();
            set.RegisterLogLevelFor<Foo>(LogLevel.Verbose);

            Assert.Equal(LogLevel.Verbose, set.FindLogLevelFor<Foo>());
        }

        [Fact]
        public void Should_Overwrite_Registered_Log_Level_With_Second_Call()
        {
            Settings set = new Settings();
            set.RegisterLogLevelFor("a.b.c", LogLevel.Fatal);
            Assert.Equal(LogLevel.Fatal, set.FindLogLevelFor("a.b.c"));

            set.RegisterLogLevelFor("a.b.c", LogLevel.Verbose);
            Assert.Equal(LogLevel.Verbose, set.FindLogLevelFor("a.b.c"));
        }

        [Fact]
        public void Should_Throw_If_Find_Level_With_Null_Name()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.FindLogLevelFor((string)null), "name");
        }

        [Fact]
        public void Should_Throw_If_Find_Level_With_Null_Type()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.FindLogLevelFor((Type)null), "type");
        }

        [Fact]
        public void Should_Throw_If_Register_Level_With_Null_Name()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterLogLevelFor((string)null, LogLevel.Fatal), "name");
        }

        [Fact]
        public void Should_Throw_If_Register_Level_With_Null_Type()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterLogLevelFor((Type)null, LogLevel.Fatal), "type");
        }

        [Fact]
        public void Should_Find_Registered_Log_Level_Of_Parent_Name_For_Child_Name()
        {
            Settings set = new Settings();

            // register the parent...
            set.RegisterLogLevelFor("a.b.c", LogLevel.Fatal);

            // lookup a child...
            Assert.Equal(LogLevel.Fatal, set.FindLogLevelFor("a.b.c.d"));
            Assert.Equal(LogLevel.Fatal, set.FindLogLevelFor("a.b.c.d.e"));
        }

        #endregion

        #region Message Formatter Tests

        [Fact]
        public void Should_Return_Default_Message_Formatter_For_Non_Registered_Name()
        {
            Settings set = new Settings();

            Assert.Same(DefaultFormatters.Message, set.FindMessageFormatterFor("a.b.c"));
        }

        [Fact]
        public void Should_Return_Default_Message_Formatter_For_Non_Registered_Type()
        {
            Settings set = new Settings();

            Assert.Same(DefaultFormatters.Message, set.FindMessageFormatterFor(typeof(Foo<string,DateTime>)));
        }

        [Fact]
        public void Should_Return_Default_Message_Formatter_For_Non_Registered_Type_Using_Generic()
        {
            Settings set = new Settings();

            Assert.Same(DefaultFormatters.Message, set.FindMessageFormatterFor<Foo<string, DateTime>.Inner>());
        }

        [Fact]
        public void Should_Throw_If_Find_Message_Formatter_With_Null_Name()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.FindMessageFormatterFor((string)null), "name");
        }

        [Fact]
        public void Should_Throw_If_Find_Message_Formatter_With_Null_Type()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.FindMessageFormatterFor((Type)null), "type");
        }

        [Fact]
        public void Should_Return_Registered_Message_Formatter_For_Logger_Name()
        {
            MessageFormatter custom = (a, b, c) => string.Empty;
            Settings set = new Settings();
            set.RegisterMessageFormatterFor("a.b.c", custom);

            Assert.Same(custom, set.FindMessageFormatterFor("a.b.c"));
        }

        [Fact]
        public void Should_Return_Registered_Message_Formatter_For_Logger_Type()
        {
            MessageFormatter custom = (a, b, c) => string.Empty;
            Settings set = new Settings();
            set.RegisterMessageFormatterFor(typeof(Foo<string>.Inner.Inner2), custom);

            Assert.Same(custom, set.FindMessageFormatterFor(typeof(Foo<string>.Inner.Inner2)));
        }

        [Fact]
        public void Should_Return_Registered_Message_Formatter_For_Logger_Type_Generic()
        {
            MessageFormatter custom = (a, b, c) => string.Empty;
            Settings set = new Settings();
            set.RegisterMessageFormatterFor<Foo<string>.Inner.Inner2>(custom);

            Assert.Same(custom, set.FindMessageFormatterFor<Foo<string>.Inner.Inner2>());
        }

        [Fact]
        public void Should_Throw_If_Register_Message_Formatter_With_Null_Name()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterMessageFormatterFor((string)null, null), "name");
        }

        [Fact]
        public void Should_Throw_If_Register_Message_Formatter_With_Name_And_Null_Formatter()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterMessageFormatterFor("a", null), "formatter");
        }

        [Fact]
        public void Should_Throw_If_Register_Message_Formatter_With_Null_Type()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterMessageFormatterFor((Type)null, null), "type");
        }

        [Fact]
        public void Should_Throw_If_Register_Message_Formatter_With_Type_And_Null_Formatter()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterMessageFormatterFor(typeof(string), null), "formatter");
        }

        [Fact]
        public void Should_Throw_If_Register_Message_Formatter_Generic_With_Null_Formatter()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterMessageFormatterFor<string>(null), "formatter");
        }

        [Fact]
        public void Should_Return_Custom_Message_Formatter_For_Child_Of_Registered_Parent()
        {
            MessageFormatter custom = (a, b, c) => string.Empty;
            Settings set = new Settings();
            set.RegisterMessageFormatterFor("a.b.c", custom);

            Assert.Same(custom, set.FindMessageFormatterFor("a.b.c.d.e"));
            Assert.Same(custom, set.FindMessageFormatterFor("a.b.c.f.g"));
            Assert.Same(DefaultFormatters.Message, set.FindMessageFormatterFor("a.b.h"));
        }

        #endregion

        #region Exception Formatter Tests

        [Fact]
        public void Should_Return_Default_Exception_Formatter_For_Non_Registered_Name()
        {
            Settings set = new Settings();

            Assert.Same(DefaultFormatters.Exception, set.FindExceptionFormatterFor("a.b.c"));
        }

        [Fact]
        public void Should_Return_Default_Exception_Formatter_For_Non_Registered_Type()
        {
            Settings set = new Settings();

            Assert.Same(DefaultFormatters.Exception, set.FindExceptionFormatterFor(typeof(Foo<string, DateTime>)));
        }

        [Fact]
        public void Should_Return_Default_Exception_Formatter_For_Non_Registered_Type_Using_Generic()
        {
            Settings set = new Settings();

            Assert.Same(DefaultFormatters.Exception, set.FindExceptionFormatterFor<Foo<string, DateTime>.Inner>());
        }

        [Fact]
        public void Should_Throw_If_Find_Exception_Formatter_With_Null_Name()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.FindExceptionFormatterFor((string)null), "name");
        }

        [Fact]
        public void Should_Throw_If_Find_Exception_Formatter_With_Null_Type()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.FindExceptionFormatterFor((Type)null), "type");
        }

        [Fact]
        public void Should_Return_Registered_Exception_Formatter_For_Logger_Name()
        {
            ExceptionFormatter custom = (a, b, c) => string.Empty;
            Settings set = new Settings();
            set.RegisterExceptionFormatterFor("a.b.c", custom);

            Assert.Same(custom, set.FindExceptionFormatterFor("a.b.c"));
        }

        [Fact]
        public void Should_Return_Registered_Exception_Formatter_For_Logger_Type()
        {
            ExceptionFormatter custom = (a, b, c) => string.Empty;
            Settings set = new Settings();
            set.RegisterExceptionFormatterFor(typeof(Foo<string>.Inner.Inner2), custom);

            Assert.Same(custom, set.FindExceptionFormatterFor(typeof(Foo<string>.Inner.Inner2)));
        }

        [Fact]
        public void Should_Return_Registered_Exception_Formatter_For_Logger_Type_Generic()
        {
            ExceptionFormatter custom = (a, b, c) => string.Empty;
            Settings set = new Settings();
            set.RegisterExceptionFormatterFor<Foo<string>.Inner.Inner2>(custom);

            Assert.Same(custom, set.FindExceptionFormatterFor<Foo<string>.Inner.Inner2>());
        }

        [Fact]
        public void Should_Throw_If_Register_Exception_Formatter_With_Null_Name()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterExceptionFormatterFor((string)null, null), "name");
        }

        [Fact]
        public void Should_Throw_If_Register_Exception_Formatter_With_Name_And_Null_Formatter()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterExceptionFormatterFor("a", null), "formatter");
        }

        [Fact]
        public void Should_Throw_If_Register_Exception_Formatter_With_Null_Type()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterExceptionFormatterFor((Type)null, null), "type");
        }

        [Fact]
        public void Should_Throw_If_Register_Exception_Formatter_With_Type_And_Null_Formatter()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterExceptionFormatterFor(typeof(string), null), "formatter");
        }

        [Fact]
        public void Should_Throw_If_Register_Exception_Formatter_Generic_With_Null_Formatter()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterExceptionFormatterFor<string>(null), "formatter");
        }

        [Fact]
        public void Should_Return_Custom_Exception_Formatter_For_Child_Of_Registered_Parent()
        {
            ExceptionFormatter custom = (a, b, c) => string.Empty;
            Settings set = new Settings();
            set.RegisterExceptionFormatterFor("a.b.c", custom);

            Assert.Same(custom, set.FindExceptionFormatterFor("a.b.c.d.e"));
            Assert.Same(custom, set.FindExceptionFormatterFor("a.b.c.f.g"));
            Assert.Same(DefaultFormatters.Exception, set.FindExceptionFormatterFor("a.b.h"));
        }

        #endregion

        #region Log Tests

        [Fact]
        public void Should_Return_Default_Log_For_Non_Registered_Name()
        {
            Settings set = new Settings();

            Assert.Same(set.DefaultLog, set.FindLogFor("a.b.c"));
        }

        [Fact]
        public void Should_Return_Default_Log_For_Non_Registered_Type()
        {
            Settings set = new Settings();

            Assert.Same(set.DefaultLog, set.FindLogFor(typeof(Foo<string, DateTime>)));
        }

        [Fact]
        public void Should_Return_Default_Log_For_Non_Registered_Type_Using_Generic()
        {
            Settings set = new Settings();

            Assert.Same(set.DefaultLog, set.FindLogFor<Foo<string, DateTime>.Inner>());
        }

        [Fact]
        public void Should_Throw_If_Find_Log_With_Null_Name()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.FindLogFor((string)null), "name");
        }

        [Fact]
        public void Should_Throw_If_Find_Log_With_Null_Type()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.FindLogFor((Type)null), "type");
        }

        [Fact]
        public void Should_Return_Registered_Log_For_Logger_Name()
        {
            Log custom = new TestLog();
            Settings set = new Settings();
            set.RegisterLogFor("a.b.c", custom);

            Assert.Same(custom, set.FindLogFor("a.b.c"));
        }

        [Fact]
        public void Should_Return_Registered_Log_For_Logger_Type()
        {
            Log custom = new TestLog();
            Settings set = new Settings();
            set.RegisterLogFor(typeof(Foo<string>.Inner.Inner2), custom);

            Assert.Same(custom, set.FindLogFor(typeof(Foo<string>.Inner.Inner2)));
        }

        [Fact]
        public void Should_Return_Registered_Log_For_Logger_Type_Generic()
        {
            Log custom = new TestLog();
            Settings set = new Settings();
            set.RegisterLogFor<Foo<string>.Inner.Inner2>(custom);

            Assert.Same(custom, set.FindLogFor<Foo<string>.Inner.Inner2>());
        }

        [Fact]
        public void Should_Throw_If_Register_Log_With_Null_Name()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterLogFor((string)null, null), "name");
        }

        [Fact]
        public void Should_Throw_If_Register_Log_With_Name_And_Null_Log()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterLogFor("a", null), "log");
        }

        [Fact]
        public void Should_Throw_If_Register_Log_With_Null_Type()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterLogFor((Type)null, null), "type");
        }

        [Fact]
        public void Should_Throw_If_Register_Log_With_Type_And_Null_Log()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterLogFor(typeof(string), null), "log");
        }

        [Fact]
        public void Should_Throw_If_Register_Log_Generic_With_Null_Log()
        {
            Execute_ArgumentNullException_Validation(
                (set) => set.RegisterLogFor<string>(null), "log");
        }

        [Fact]
        public void Should_Return_Custom_Log_For_Child_Of_Registered_Parent()
        {
            Log custom = new TestLog();
            Settings set = new Settings();
            set.RegisterLogFor("a.b.c", custom);

            Assert.Same(custom, set.FindLogFor("a.b.c.d.e"));
            Assert.Same(custom, set.FindLogFor("a.b.c.f.g"));
            Assert.Same(set.DefaultLog, set.FindLogFor("a.b.h"));
        }

        #endregion

        private void Execute_ArgumentNullException_Validation(Action<Settings> throws, string paramName)
        {
            Settings set = new Settings();

            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(() => throws(set));
            Assert.Equal(paramName, ex.ParamName);
        }
    }
}
